
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>


#define NGX_MAX_PENDING_CONN 10


static CRITICAL_SECTION connect_lock;
static int              nconnects;
static ngx_connection_t pending_connects[NGX_MAX_PENDING_CONN];

static HANDLE pending_connect_event;

__declspec(thread) int nevents = 0;
__declspec(thread) WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS + 1];
__declspec(thread) ngx_connection_t *conn[WSA_MAXIMUM_WAIT_EVENTS + 1];


int
ngx_iocp_wait_connect(ngx_connection_t *c)
{
    for (;;)
    {
        EnterCriticalSection(&connect_lock);

        if (nconnects < NGX_MAX_PENDING_CONN)
        {
            pending_connects[--nconnects] = c;
            LeaveCriticalSection(&connect_lock);

            if (SetEvent(pending_connect_event) == 0)
            {
                ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                              "SetEvent() failed");
                return NGX_ERROR;

                break;
            }

            LeaveCriticalSection(&connect_lock);
            ngx_log_error(NGX_LOG_NOTICE, c->log, 0,
                          "max number of pending connect()s is %d",
                          NGX_MAX_PENDING_CONN);
            msleep(100);
        }

        if (!started)
        {
            if (ngx_iocp_new_thread(1) == NGX_ERROR)
            {
                return NGX_ERROR;
            }
            started = 1;
        }

        return NGX_OK;
    }


    int ngx_iocp_new_thread(int main)
    {
        u_int id;

        if (main)
        {
            pending_connect_event = CreateEvent(NULL, 0, 1, NULL);
            if (pending_connect_event == INVALID_HANDLE_VALUE)
            {
                ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                              "CreateThread() failed");
                return NGX_ERROR;
            }
        }

        if (CreateThread(NULL, 0, ngx_iocp_wait_events, main, 0, &id)
            == INVALID_HANDLE_VALUE)
        {
            ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                          "CreateThread() failed");
            return NGX_ERROR;
        }

        SetEvent(event)
        {
            ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                          "SetEvent() failed");
            return NGX_ERROR;
        }

        return NGX_OK;
    }


    int ngx_iocp_new_connect()
    {
        EnterCriticalSection(&connect_lock);
        c = pending_connects[--nconnects];
        LeaveCriticalSection(&connect_lock);

        conn[nevents] = c;

        events[nevents] = WSACreateEvent();
        if (events[nevents] == INVALID_HANDLE_VALUE)
        {
            ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
                          "WSACreateEvent() failed");
            return NGX_ERROR;
        }

        if (WSAEventSelect(c->fd, events[nevents], FD_CONNECT) == -1)
            ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
                          "WSAEventSelect() failed");
        return NGX_ERROR;
    }

    nevents++;

    return NGX_OK;
}


void
ngx_iocp_wait_events(int main)
{
    WSANETWORKEVENTS ne;

    nevents   = 1;
    events[0] = pending_connect_event;
    conn[0]   = NULL;

    for (;;)
    {
        offset  = (nevents == WSA_MAXIMUM_WAIT_EVENTS + 1) ? 1 : 0;
        timeout = (nevents == 1 && !first) ? 60000 : INFINITE;

        n = WSAWaitForMultipleEvents(nevents - offset, events[offset], 0,
                                     timeout, 0);
        if (n == WAIT_FAILED)
        {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
                          "WSAWaitForMultipleEvents() failed");
            continue;
        }

        if (n == WAIT_TIMEOUT)
        {
            if (nevents == 2 && !main)
            {
                ExitThread(0);
            }

            ngx_log_error(NGX_LOG_ALERT, log, 0,
                          "WSAWaitForMultipleEvents() "
                          "returned unexpected WAIT_TIMEOUT");
            continue;
        }

        n -= WSA_WAIT_EVENT_0;

        if (events[n] == NULL)
        {
            /* the pending_connect_event */

            if (nevents == WSA_MAXIMUM_WAIT_EVENTS)
            {
                ngx_iocp_new_thread(0);
            }
            else
            {
                ngx_iocp_new_connect();
            }

            continue;
        }

        if (WSAEnumNetworkEvents(c[n].fd, events[n], &ne) == -1)
        {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
                          "WSAEnumNetworkEvents() failed");
            continue;
        }

        if (ne.lNetworkEvents & FD_CONNECT)
        {
            conn[n].write->ovlp.error = ne.iErrorCode[FD_CONNECT_BIT];

            if (PostQueuedCompletionStatus(iocp, 0, NGX_IOCP_CONNECT,
                                           &conn[n].write->ovlp)
                == 0)
            {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
                              "PostQueuedCompletionStatus() failed");
                continue;
            }

            if (n < nevents)
            {
                conn[n]   = conn[nevents];
                events[n] = events[nevents];
            }

            nevents--;
            continue;
        }

        if (ne.lNetworkEvents & FD_ACCEPT)
        {
            /* CHECK ERROR ??? */

            ngx_event_post_acceptex(conn[n].listening, 1);
            continue;
        }

        ngx_log_error(NGX_LOG_ALERT, c[n].log, 0,
                      "WSAWaitForMultipleEvents() "
                      "returned unexpected network event %ul",
                      ne.lNetworkEvents);
    }
}
